This page last changed on Sep 14, 2011 by kristin.bradley@involver.com.

In Chapter 5, we built a fully functioning contest that allows users submit photos with validations, admins can moderate the entries, and we were able to display all approved entries.

This chapter will cover advanced functionality - extending the gallery with pagination & voting, adding category filtering & building a standalone canvas view.

Adding Pagination

This contest is going to likely receive hundreds of entries so we'll want to paginate the results. Let's add a paginate helper block to help drive this.

{% partial name: "form" %}
  {% contest_form %}
  <script>
    var rules = {
      'registration[email]': {required: true},
      'submission[celeb]': {required: true},
      'submission[before_photo]': {required: true},
      'submission[after_photo]': {required: true}
    };
    </script>

    <h4>Celeb Photo Contest Entry Form</h4>

    {% unless contest_user_is_registered %}
      <div>
        Name: <input type="text" name="registration[name]"><br>
        Email: <input type="text" name="registration[email]"><br>
      </div>
    {% endunless %}

    <hr>

    Celeb: <input type="text" name="submission[celeb]"><br>
    Before Photo: {% image_upload_field name:'submission[before_photo]' %}<br>
    After Photo: {% image_upload_field name:'submission[after_photo]' %}<br>

    <div>
      <input type="submit" value="Submit">
    </div>
  {% endcontest_form %}
{% endpartial %}

{% partial name: "confirmation" %}
  <h4>Success!</h4>
  <p>Thank you, <strong>{{ contest_user.name }}</strong> for submitting your entry.</p>

  <div>
    Email: {{ contest_entry.registration.email }}<br>
    Celebrity: {{ contest_entry.submission.celeb }}<br>
    Before Photo: <img src="{{ contest_entry.submission.before_photo }}"><br>
    After Photo: <img src="{{ contest_entry.submission.after_photo }}">
  </div>
{% endpartial %}

<h3>Blush Magazine Celeb Photo Contest</h3>
<p>Show us your favorite before and after photos of celebs!</p>

<hr>

{% contest %}
    <p>{% contest_form_link entry_form_partial: "form" success_partial: "confirmation" %}
        Click here to enter
    {% endcontest_form_link %}</p>

    {% paginate contest.contest_entries per_page:1 order:"created_at DESC" %}
        <ul>
            {% for entry in contest_entries %}
            <li>
                <p><strong>{{ entry.submission.celeb }}</strong>
                submitted by {{ entry.contest_user.name }}</p>

                <div>
                    <h3>{{ entry.submission.celeb }}</h3>
                    {{ entry.submission.before_photo | resize_to: "200" | img_tag }}
                    {{ entry.submission.after_photo | resize_to: "200" | img_tag }}
                </div>
            </li>
            {% endfor %}
        </ul>

        {{ pagination_links }}
    {% endpaginate %}
{% endcontest %}

We're now showing 1 entry in the gallery at a time with the ability to page through more entries.

We've also changed the default ordering of entries in the gallery such that the most recent entries are shown first.

This ordering may not be desirable for all users. Furthermore, as our gallery gets larger we'll want to give users more ways to explore submissions.

Custom Ordering

Let's update our gallery to enable custom ordering. We'll first need to extract our gallery into a partial.

  1. Update your code to now read:
    {% partial name: "form" %}
    
      <script>
        var rules = {
          'registration[email]': {required: true},
          'submission[celeb]': {required: true},
          'submission[before_photo]': {required: true},
          'submission[after_photo]': {required: true}
        };
      </script>
    
      {% contest_form rules_var: "rules" %}
    
        <h4>Celeb Photo Contest Entry Form</h4>
    
        {% unless contest_user_is_registered %}
          <div>
            Name: <input type="text" name="registration[name]"><br>
            Email: <input type="text" name="registration[email]"><br>
          </div>
        {% endunless %}
    
        <hr>
    
        Celeb: <input type="text" name="submission[celeb]"><br>
        Before Photo: {% image_upload_field name:'submission[before_photo]' %}<br>
        After Photo: {% image_upload_field name:'submission[after_photo]' %}<br>
    
        <div>
          <input type="submit" value="Submit"/>
        </div>
      {% endcontest_form %}
    {% endpartial %}
    
    {% partial name: "confirmation" %}
      <h4>Success!</h4>
      <p>Thank you, <strong>{{ contest_user.name }}</strong> for submitting your entry.</p>
    
      <div>
        Email: {{ contest_entry.registration.email }}<br>
        Before Photo: <img src="{{ contest_entry.submission.before_photo }}" alt=""><br>
        After Photo: <img src="{{ contest_entry.submission.after_photo }}" alt="">
      </div>
    {% endpartial %}
    
    {% partial name: "gallery" %}
      {% paginate contest.contest_entries per_page: 3 order: "created_at DESC" %}
        {% for entry in contest_entries %}
          <div>
            <p><strong>{{ entry.submission.celeb }}</strong>
            submitted by {{ entry.contest_user.name }}</p>
    
            <div>
    	  <h3>{{ entry.submission.celeb }}</h3>
              {{ entry.submission.before_photo | resize_to: "200" | image_tag }}
              {{ entry.submission.after_photo | resize_to: "200" | image_tag }}
            </div>
          </div>
        {% endfor %}
    
        {{ pagination_links }}
     {% endpaginate %}
    {% endpartial %}
    
    <h3>Blush Magazine Celeb Photo Contest</h3>
    <p>Show us your favorite before and after photos of celebs!</p>
    
    <hr>
    
    {% contest %}
      <p>{% contest_form_link entry_form_partial: "form" success_partial: "confirmation" %}
         Click here to enter
      {% endcontest_form_link %}</p>
    
      {% render partial: "gallery" contest: contest %}
    {% endcontest %}
    
  1. If all goes well, you should see the same exact results - nothing has changed to the end-user

We've made a few changes to our code:

  • First, we've extracted our gallery code into a partial which we've named 'gallery'
  • Next, we've replaced our original gallery code with a reference to the gallery partial using the render tag
  • The render tag requires a name of a partial as well as concrete values for any variables referenced inside your partial
  • Note that we don't have to use `contest` as the name of the variable in our partial – it could be c, for instance – we just have to be consistent at render time

Note that all of these changes were internal, no functionality in our application has changed.

  1. Now that our gallery is in a partial, we can make the order option a variable
  2. Update the gallery partial to now read:
{% partial name: "gallery" %}
  {% paginate contest.contest_entries per_page: 3 order: order_by %}
    {% for entry in contest_entries %}
      <div>
        <p><strong>{{ entry.submission.celeb }}</strong>
        submitted by {{ entry.contest_user.name }}</p>
        <div>
	  <h3>{{ entry.submission.celeb }}</h3>
          {{ entry.submission.before_photo | resize_to: "200" | image_tag }}
          {{ entry.submission.after_photo | resize_to: "200" | image_tag }}
        </div>
      </div>
    {% endfor %}
    {{ pagination_links }}
  {% endpaginate %}
{% endpartial %}
  1. Next, update the render call to pass in a default value for the new order_by variable we've introduced:
    {% render partial: "gallery" contest: contest order_by: "created_at DESC" %}
    

Late-Binding and ajax_link

Passing in the value of the contest & order_by variables in our render tag is an example of late-binding. Late-binding allows you to specify the values of variables at a later time.

Using late-binding with the render tag may not seem that special, we're still providing the values up front, we've just restructured our code to render the gallery as a partial.

However, there is an additional way to render a partial: using the ajax_link tag. The ajax_link tag enables you to render a partial whenever a user clicks on a link.

Late-binding combined with the ajax_link tag is a very powerful facility. We are no longer limited to rendering SML once at page load, but at any point in our application.

Using this facility you can build sophisticated functionality such as custom ordering, which we'll now introduce in our contest:

  1. Update your code to now read:
    {% partial name: "form" %}
    
      <script>
        var rules = {
          'registration[email]': {required: true},
          'submission[celeb]': {required: true},
          'submission[before_photo]': {required: true},
          'submission[after_photo]': {required: true}
        };
      </script>
    
      {% contest_form rules_var: "rules" %}
    
        <h4>Celeb Photo Contest Entry Form</h4>
    
        {% unless contest_user_is_registered %}
          <div>
            Name: <input type="text" name="registration[name]"><br>
            Email: <input type="text" name="registration[email]"><br>
          </div>
        {% endunless %}
    
        <hr>
    
        Celeb: <input type="text" name="submission[celeb]"><br>
        Before Photo: {% image_upload_field name:'submission[before_photo]' %}<br>
        After Photo: {% image_upload_field name:'submission[after_photo]' %}<br>
    
        <div>
          <input type="submit" value="Submit">
        </div>
      {% endcontest_form %}
    {% endpartial %}
    
    {% partial name: "confirmation" %}
      <h4>Success!</h4>
      <p>Thank you, <strong>{{ contest_user.name }}</strong> for submitting your entry.</p>
    
      <div>
        Email: {{ contest_entry.registration.email }}<br>
        Celeb: {{ contest_entry.registration.email }}<br>
        Before Photo: <img src="{{ contest_entry.submission.before_photo }}"><br>
        After Photo: <img src="{{ contest_entry.submission.after_photo }}">
      </div>
    
    {% endpartial %}
    
    {% partial name: "gallery" %}
      {% paginate contest.contest_entries per_page: 3 order: order_by %}
        {% for entry in contest_entries %}
          <div>
            <p><strong>{{ entry.submission.celeb }}</strong>
            submitted by {{ entry.contest_user.name }}</p>
    
            <div>
    	  <h3>{{ entry.submission.celeb }}</h3>
              {{ entry.submission.before_photo | resize_to: "200" | image_tag }}
              {{ entry.submission.after_photo | resize_to: "200" | image_tag }}
            </div>
          </div>
        {% endfor %}
    
        {{ pagination_links }}
      {% endpaginate %}
    {% endpartial %}
    
    <h3>Blush Magazine Celeb Photo Contest</h3>
    <p>Show us your favorite before and after photos of celebs!</p>
    <hr>
    
    {% contest %}
      <p>{% contest_form_link entry_form_partial: "form" success_partial: "confirmation" %}
         Click here to enter
      {% endcontest_form_link %}
      </p>
    
      {% ajax_link partial: 'gallery' container: 'gallery_container' contest: contest order_by: "created_at DESC" %}
        Show newest first
      {% endajax_link %} |
      {% ajax_link partial: 'gallery' container: 'gallery_container' contest: contest order_by: "created_at ASC" %}
        Show oldest first
      {% endajax_link %}
    
      <div id="gallery_container">
        {% render partial: "gallery" contest: contest order_by: "created_at DESC" %}
      </div>
    {% endcontest %}
    
  1. You should now be able to dynamically change the order of the gallery by clicking the "Show oldest first" and "Show newest first" links.

Let's take a minute to break down the code changes:

  • First we've wrapped our rendered gallery in a root div container giving it the ID "gallery_container"
  • Next, we've added two ajax_links which when clicked will allow users to change the ordering of entries in the gallery
  • The ajax_link tag requires a name of a partial and a name of a containing element to target, as well as values for any variables referenced inside your partial
  • When a user clicks on one of our ajax_links, the gallery will re-render with a different order_by value and the result will show in the gallery_container (replacing the gallery we render on page load)
Document generated by Confluence on Feb 12, 2013 09:09